import _ from "lodash";
import fs from "fs-extra";
import path from "path";
import { toolchain } from "../../toolchain.js";
import { replaceInFile, replaceInFileOrThrow, writeUTF8withBom } from "../../tools/vendor.js";
import { BaseTask, TaskResult } from "./BastTask.js";
import { CommonEnv } from "../CommonEnv.js";
import { incVersionCode } from "../../CommonAction.js";


export interface PrebuildAppData {
    defines: string
    versionCode: string
}

export class PreBuildAppTask extends BaseTask<PrebuildAppData> {
    async run(): Promise<TaskResult<PrebuildAppData>> {
        // 修改ResLoader.cs中的tiShenSvr
        if (toolchain.option.platform == 'WebGL' && toolchain.option.webglRuntime != 'browser') {
            const channelCfg = toolchain.params.channelCfg;
            if (channelCfg.tishen_svr) {
                if (channelCfg.tishen_svr.host.includes('://')) {
                    throw new Error('tishen_svr host must not include protocol');
                }
                const tiShenSvr = channelCfg.tishen_svr.host.replace(':', ',') + ',' + channelCfg.tishen_svr.id;
                const resLoaderFile = path.join(toolchain.params.workSpacePath, CommonEnv.RESLOADER_CS);
                await replaceInFileOrThrow(resLoaderFile, 'tiShenSvr = "???"', `tiShenSvr = "${tiShenSvr}"`);
            }
        }
        this._createChannelScriptByCfg();
        const defines = await this.createBuildDefinesScript();
        await this._restoreBuildinTxtFile();
        await this.confuseAll();
        await this.copyBuildinAssets();
        const versionCode = await incVersionCode(path.join(toolchain.params.workSpacePath, CommonEnv.VersionCodeTxt));
        return { success: true, errorCode: 0, data:  { defines, versionCode } };
    }

    private _createChannelScriptByCfg(): void {
        console.log('@@ create channel script by cfg');
        const configFile = path.join(toolchain.params.workSpacePath, 'Assets/Scripts/game/Config.cs');
        const channelCfg = toolchain.params.channelCfg;
        const url = channelCfg.url;
        const jsonconfigpath = path.join(toolchain.params.workSpacePath, 'Assets/Resources/config.json');
        if(fs.existsSync(jsonconfigpath)) {
            const jsonData = {
                "remote": url, 
                "plat": channelCfg.plat, 
                "gameid": channelCfg.gameid, 
                "productName": channelCfg.productName, 
                "bundleId": channelCfg.bundleId, 
                "apkpath": channelCfg.path
            };
            writeUTF8withBom(jsonconfigpath, JSON.stringify(jsonData));
        } else {
            const s = `
            // auto create by builder
            namespace game
            {
                public class Config
                {
                    public static string remoteResUrl = "${url}";
                    public static int plat = ${channelCfg.plat};
                    public static int gameid = ${channelCfg.gameid};
                    public static readonly string productName = "${channelCfg.productName}";
                    public static readonly string bundleId = "${channelCfg.bundleId}";
                    public static readonly string apkpath = "${channelCfg.path}";
                    public static readonly uint steamAppId = ${channelCfg.steamAppId || 0};
                }
            }
            `;
            writeUTF8withBom(configFile, s);
        }
    }
    
    private async createBuildDefinesScript(): Promise<string> {
        const defines = _.uniq([toolchain.params.channelCfg.defines, toolchain.params.defines].filter((value) => Boolean(value))).join('.');
        console.log('create build defines script for ts:', defines);
        const definefile = path.join(toolchain.params.workSpacePath, 'Assets/Scripts/game/BuildDefines.cs');
        const oldContent = await fs.readFile(definefile, 'utf-8');
        const s = oldContent.replace('defines = ""', `defines = "${defines}"`);
        // 注：现在的文件格式很乱，有的是utf8，有的是utf8-bom，这里只是延续之前的做法，并没有什么特殊考量
        writeUTF8withBom(definefile, s);
        return defines;
    }
    
    private async _restoreBuildinTxtFile(): Promise<void> {
        const back = path.join(toolchain.params.workSpacePath, 'publish/buildinAB_bak.txt');
        if (fs.existsSync(back)){
            const src = path.join(toolchain.params.workSpacePath, 'publish/buildinAB.txt');
            await fs.copyFile(back, src);
        }
    }

    private async confuseAll(): Promise<void> {
        if (!toolchain.params.channelCfg.randomSeed) return;
        const params = ['-batchmode', '-projectPath', toolchain.params.workSpacePath, '-executeMethod', 'BuildCommand.ConfuseAll', 
        '-randomSeed', toolchain.params.channelCfg.randomSeed, '-quit'];
        await toolchain.unity.runUnityCommand(this.cmdOption, params, 'ConfuseAll');
    }
      
    private async copyBuildinAssets() {
        if (toolchain.option.platform == 'WebGL') {
            console.log('webgl skip copy buildin');
            return;
        }

        this._addSpecAB2BuildInList();
    
        console.log('@@ copy buildin assets');

        let uploadPath = toolchain.params.uploadPath;
        if(uploadPath[uploadPath.length - 1] != path.sep) uploadPath += path.sep;
        let params = ['-batchmode', '-projectPath', toolchain.params.workSpacePath, '-executeMethod', 'BuildCommand.CopyBuildinAssets', 
        '-UploadResDir', uploadPath, '-platform', this.cmdOption.platform, '-quit'];

        const buildinKey = toolchain.params.channelCfg.buildinKey;
        if(buildinKey) {
            params.push('-buildinKey', buildinKey);
        }
        await toolchain.unity.runUnityCommand(this.cmdOption, params, 'BuildIn');
    }
    
    private _addSpecAB2BuildInList() {
        const buildinABfile = path.join(toolchain.params.workSpacePath, 'publish/buildinAB.txt');
        
        const baseurl = path.join(toolchain.params.workSpacePath, 'publish/assets', this.cmdOption.platform.toLowerCase());        
        console.log('@ baseurl:' + baseurl);
    
        let s: string;
        if (fs.existsSync(buildinABfile)) {
            s = fs.readFileSync(buildinABfile, 'utf-8');
            s = _.trimEnd(s, '\r\n').replace(/\r\n/g, '\n');
        } else {
            s = '';
        }
    
        let hascopy = false;
        // 优先copy第一个，第一个不存在再copy第二个，二选一
        const spec_ab_buildins = [ 
            ['channel/loadingpage/{gameid}/0.png', 'channel/loadingpage/{gameid}/0.jpg', 'images/loadingpage/{gameid}/0.png', 'images/loadingpage/{gameid}/0.jpg', 'images/loadingpage/0.png', 'images/loadingpage/0.jpg']
            ,['channel/loginpage/{gameid}/back.png', '']
            ,['channel/logo/{gameid}.png', '']
        ];
        
        const projAssetsPath = path.join(toolchain.params.workSpacePath, 'Assets/AssetSources');
        for(let absrcPair of spec_ab_buildins) {
            for(let oneAb of absrcPair) {
                if(oneAb == '') continue;
                const absrc = oneAb.replace('{gameid}', String(toolchain.params.channelCfg.gameid));
                // 兼容老项目，ab包去掉了后缀
                const absrcAb = absrc.replace(/\.\w+$/, '.ab');
                // 新项目ab包加了后缀
                const absrcAb2 = absrc + '.ab';
                for(let ab of [absrcAb2, absrcAb]) {
                    if(fs.existsSync(path.join(projAssetsPath, absrc)) && fs.existsSync(path.join(baseurl, ab))) {
                        console.log('@ absrcAb:' + ab)
                        if(!s.includes(ab)) {
                            s = s + '\n' + ab;
                            hascopy = true;
                        }
                        break;
                    }
                }
            }
        }
    
        if(!hascopy) return;
    
        console.log('@@ copy spec channel ab to buildin assets');
        fs.writeFileSync(buildinABfile, s, 'utf-8');
    }

    get skip(): boolean {
        return !this.cmdOption.buildExe;
    }
}
